import java.io.File;
import java.io.FileNotFoundException;
import java.util.AbstractMap.SimpleEntry;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * trieda na nahravanie dat z BiBTeXu a ich udrzovanie a sprostredkovanie inym triedam
 * @author cerko
 *
 */
public class BiBLoader {
	
	private File bibTex;
	private Scanner scanner;
	
	private String lineBreak;
	
	//debug veci
	private Set<String> setkyTypy;
	private Map<String, Data> library;
	
	/**
	 * ked chceme vytvorit novy bib z TeXu
	 */
	BiBLoader(){
		
	}
	
	/**
	 * ked nahravame uz eistujucu lib
	 * @param bibTex - bibtex lib
	 * @throws FileNotFoundException 
	 */
	BiBLoader(File bibTex) throws FileNotFoundException{
		//debug veci
		setkyTypy = new HashSet<String>();
		library = new TreeMap<String, Data>(String.CASE_INSENSITIVE_ORDER);
		lineBreak = System.getProperty("line.separator");
		
		this.bibTex = bibTex;
		if (bibTex.exists()){
			scanFile();
		}
		
	}
	
	/**
	 * naskenuje len velke bloky rozdelene '@'
	 * @throws FileNotFoundException
	 */
	public void scanFile() throws FileNotFoundException{
		scanner = new Scanner(bibTex);
		scanner.useDelimiter("@");
		while (scanner.hasNext()){
			scanType(scanner.next());
		}
		scanner.close();
		//debug helpers
		/*
		System.out.println(setkyTypy.toString());
		
		for (Entry<String, Data> entry : library.entrySet()){
			String key = entry.getKey();
			Data value = entry.getValue();
			
			System.out.println("{" + key + " = " + value.toString() + " }");
		}
		*/
	}
	
	/**
	 * zisti typ bloku a zvysok v {} posle dalej
	 * @param typeBlock - blok textu z bib este s typom
	 */
	public void scanType(String typeBlock){
		Scanner typeScanner = new Scanner(typeBlock);
		typeScanner.useDelimiter("\\{");
		String type = typeScanner.next();
		setkyTypy.add(type);
		typeScanner.useDelimiter("");
		String whatsLeft = "";
		while (typeScanner.hasNext()){
			whatsLeft += typeScanner.next();
		}
		typeScanner.close();
		//System.out.println(whatsLeft);
		scanItem(type, whatsLeft);
	}
	
	/**
	 * naskenuje meno polozky a zvysok posle do scanItemsValues
	 * @param type - typ polozky
	 * @param itemBlock - skenovany text
	 */
	public void scanItem(String type, String itemBlock){
		Matcher matcher = Pattern.compile("(?<=\\{)(.*)(?=,)").matcher(itemBlock);
		if (matcher.find()){
			//System.out.println(matcher.group());
			//System.out.println(itemBlock.substring(matcher.end()));
			scanItemsValues(type, matcher.group(), itemBlock.substring(matcher.end()));
		}
		else {
			/*
			System.out.println("Unable to read bib reference : ");
			System.out.println(itemBlock);
			*/
		}
		
	}
	
	/**
	 * nacitava jednotlive pary key=value a posiela ich do scanBibPair
	 * @param type - typ polozky
	 * @param item - id polozky
	 * @param itemBlock - skenovany text
	 */
	public void scanItemsValues(String type, String item, String itemBlock){
		Matcher matcher = Pattern.compile(".+?},|..+?}", Pattern.DOTALL).matcher(itemBlock);
		Map<String, String> values = new HashMap<String, String>();
		SimpleEntry<String, String> bibEntry;
		while (matcher.find()){
			String bibPair = matcher.group();
			
			bibPair = bibPair.replaceAll("(?<! )" + lineBreak + " ", "");
			bibPair = bibPair.replaceAll("(?<= )" + lineBreak + "(?! )", "");
			bibPair = bibPair.replaceAll(lineBreak, " ");
			bibEntry = scanBibPair(type, item, bibPair);
			
			if (Frontend.DEBUG){
				System.out.println(bibPair);
			}
			
			if (bibEntry != null){
				values.put(bibEntry.getKey(), bibEntry.getValue());
			}
			else {
				//System.out.println("could not read bibPair : " + bibPair);
			}
			
		}
		library.put(item, new Data(type, values));
		
		
	}
	
	/**
	 * scanuje dvojicu key = value a vracia entry, ak sa nepodari nacitat vracia null
	 * @param type - typ polozky
	 * @param item - id polozky
	 * @param pairBlock - skenovany text
	 * @return entry type key = value
	 */
	public SimpleEntry<String, String> scanBibPair(String type, String item, String pairBlock){
		Matcher matcher = Pattern.compile(" *(?<key>[a-zA-Z0-9]+)=((\\{\\{(?<value2>.+)}})|(\\{(?<value1>.+)}))").matcher(pairBlock);
		if (matcher.find()){
			//System.out.println(matcher.group("key"));
			String value = "";
			if (matcher.group("value1") == null){
				value = matcher.group("value2");
			}
			else {
				value = matcher.group("value1");
			}
			//System.out.println(value);
			return new SimpleEntry<String, String>(matcher.group("key"), value);
		}
		return null;
	}
	
	/**
	 * vrati data z mapy ktore prisluchaju keyu
	 * @param key
	 * @return
	 */
	public Data getRefData(String key){
		return library.get(key);
	}
	
	/**
	 * prida referenciu do library
	 * @param type - typ referencie : book, article etc.
	 * @param reference - dvojice meno - data referencie
	 */
	public void addRef(String type, Entry<String, Data> reference){
		setkyTypy.add(type);
		if (library.containsKey(reference.getKey())){
			//TODO do logu info o zmene
			System.out.println("changed reference : " + reference.getKey());
		}
		
		library.put(reference.getKey(), reference.getValue());
	
	}
	
	public Map<String, Data> getLibrary(){
		return library;
	}
}
